#pragma once

#include <google/protobuf/service.h>

#include <map>

#include "zrpc/rpc/protobuf_codec_lite.h"
#include "zrpc/rpc/rpc.pb.h"
#include "zrpc/net/tcp_connection.h"

extern const char rpctag[];  // = "RPC0";
namespace zrpc {
// wire format
//
// Field     Length  Content
//
// size      4-byte  N+8
// "RPC0"    4-byte
// payload   N-byte
// checksum  4-byte  adler32 of "RPC0"+payload
//

typedef ProtobufCodecLiteT<RpcMessage, rpctag> RpcCodec;
}  // namespace zrpc

namespace google {
namespace protobuf {

// Defined in other files.
class Descriptor;         // descriptor.h
class ServiceDescriptor;  // descriptor.h
class MethodDescriptor;   // descriptor.h
class Message;            // message.h

class Closure;

class RpcController;
class Service;

}  // namespace protobuf
}  // namespace google

namespace zrpc {
class RpcChannel : public ::google::protobuf::RpcChannel {
 public:
  RpcChannel();

  explicit RpcChannel(const std::shared_ptr<TcpConnection>& conn);

  ~RpcChannel() override;

  void SetConnection(const std::shared_ptr<TcpConnection>& conn) {
    conn_ = conn;
  }

  void SetServices(
      const std::map<std::string, ::google::protobuf::Service*>* services) {
    services_ = services;
  }

  void CallMethod(const ::google::protobuf::MethodDescriptor* method,
                  ::google::protobuf::RpcController* controller,
                  const ::google::protobuf::Message* request,
                  ::google::protobuf::Message* response,
                  ::google::protobuf::Closure* done) override;

  void OnMessage(const std::shared_ptr<TcpConnection>& conn, Buffer* buf);

 private:
  void OnRpcMessage(const std::shared_ptr<TcpConnection>& conn,
                    const std::shared_ptr<RpcMessage>& message);

  void DoneCallback(::google::protobuf::Message* response, int64_t id);

  struct OutstandingCall {
    ::google::protobuf::Message* response;
    ::google::protobuf::Closure* done;
  };

  RpcCodec codec_;
  std::shared_ptr<TcpConnection> conn_;
  std::atomic<int64_t> id_;

  std::mutex mutex_;
  std::map<int64_t, OutstandingCall> outstandings_;
  const std::map<std::string, ::google::protobuf::Service*>* services_;
};
typedef std::shared_ptr<RpcChannel> RpcChannelPtr;

}  // namespace zrpc
